iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0

環境

OS: Windows 10
Editor: Visual Studio Code
Rust version: 1.63.0

致命錯誤

當程式發生致命錯誤的時候,通常最直接的做法是會把錯誤訊息輸出出來,然後緊急關閉程式,一樣的,Rust也有這樣的功能:

panic!("??");

執行後會印出以下訊息:

thread 'main' panicked at '??', src\main.rs:5:5

error: process didn't exit successfully: `target\debug\basic.exe` (exit code: 101)

這樣的錯誤訊息,之前也在別的篇章見過,但沒注意到的是下面還有一段訊息,可以把一步步地把錯誤往回推:

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

在教學文件中,可以遍執行的時候,多下這行指令把錯誤往回推:

RUST_BACKTRACE=1 cargo run

但這貌似只在Unix跟macOS才有效,稍微改一下,把這指令設定在main()裡面:

use std::env;

fn main() {
    env::set_var("RUST_BACKTRACE", "1");
    panic!("??");
}

Result與可復原錯誤

Result之前也有見過,在找main()是否可以回傳錯誤的時候有登場。
這個功能對比到其他語言的話就是try...catch...

他的型別是Result<T, E>,類似Option<T>,但比起空值,他給的是錯誤,以下是例子:

use std::fs::File;
// ...

let f = File::open("hello.txt");
let f = match f {
    Ok(file) => file,
    Err(error) => panic!("Failed to open file: {:?}", error),
}

{:?}這個format是debug的format,是要讓結構去實作std::fmt::Debug這個介面,才可以讓結構直接輸出成特定格式。

再來,如果給出錯誤,我們可以對於一些特定的錯去篩選跟處理,例如:

use std::fs::File;
use std::io::ErrorKind;

// ...

// 沒有檔案的話幫忙建一個
let f = File::open("hello.txt");

let f = match f {
    Ok(file) => file,
    Err(error) => {
        if error.kind() == ErrorKind::NotFound {
            match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("{:?}", e),
            }
        } else {
            panic!("{:?}", error);
        }
    }
};

上面的處理雖然詳細,但冗長,我們可以用unwrap()expect()來簡化它。

// 由系統幫忙輸出panic內容
let f = File::open("hello.txt").unwrap();
// 指定輸出apnic的內容
let f = File::open("hello.txt").expect("File to open \"hello.txt\".");

回傳錯誤

use std::fs::File;
use std::io::{self, Read};

fn read_file_to_string(filename: &str) -> Result<String, io::Error> {
    let f = File::open(filename);
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

fn main() {
    let greeting = read_file_to_string("hello.txt");
    if let Ok(content) = greeting {
        println!("{}", content);
    } else if let Err(e) = greeting {
        panic!("{:?}", e);
    }
}

試試看以上個這個範例,如果有hello.txt的話,會輸出文件的內容,沒有的話則會丟出panic

然後我們可以用**?** 運算子簡化read_file_to_string,這個運算子如果是Ok的話,則會以表達式(expression)回傳;如果是Err的話,則會連帶return,把整個函式回傳回去:

fn read_file_to_string(filename: &str) -> Result<String, io::Error> {
    let mut f = File::open(filename)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

如果要再簡化,可以這樣寫:

fn read_file_to_string(filename: &str) -> Result<String, io::Error> {
    let mut s = String::new();
    File::open(filename)?.read_to_string(&mut s)?;
    Ok(s)
}

Reference


上一篇
[Rust] 常用集合 - 雜湊表(Hash Map)
下一篇
[Rust] 泛型 (Generics)
系列文
嘗試30天學「不」會Rust18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言